package edu.zju.gis.dbfg.tileserver;

import edu.zju.gis.dbfg.common.Page;
import edu.zju.gis.dbfg.common.Result;
import edu.zju.gis.dbfg.tileserver.model.GeotrellisTileOptions;
import edu.zju.gis.dbfg.tileserver.model.MSFieldInfo;
import edu.zju.gis.dbfg.tileserver.model.MapService;
import edu.zju.gis.dbfg.tileserver.model.search.MSFieldInfoSearch;
import edu.zju.gis.dbfg.tileserver.service.MSFieldInfoService;
import edu.zju.gis.dbfg.tileserver.service.MapServiceService;
import geotrellis.raster.render.*;
import geotrellis.store.AttributeStore;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.io.IOException;
import java.math.BigInteger;
import java.security.interfaces.DSAPublicKey;
import java.util.*;
import java.util.List;

/**
 * @author Hu
 * @date 2019/11/1
 **/
@Controller
@RequestMapping("/gttileservice")
@CrossOrigin
public class GeoTrellisTileServer {

    private static final Logger logger = LoggerFactory.getLogger(GeoTrellisTileServer.class);
    private static final int DEFAULT_Z_MAX = 15;

    @Autowired
    private MapServiceService mapServiceService;

    @Autowired
    private MSFieldInfoService msFieldInfoService;

    @RequestMapping(value = "/getbands/{layerId}", method = RequestMethod.GET)
    @ResponseBody
    public Result getBands(@PathVariable("layerId") String layerId) {
        try {
            MapService mapService = mapServiceService.select(layerId);
            MSFieldInfoSearch filter = new MSFieldInfoSearch();
            filter.setServiceID(layerId);
            List<MSFieldInfo> msFieldInfos = msFieldInfoService.selectBandsById(filter);
            return Result.success().setBody(msFieldInfos);
        } catch (Exception e) {
            return Result.error().setMessage(e.getMessage());
        }
    }

    @RequestMapping(value = "/setOpt/{layerId}/{bandIndex}", method = RequestMethod.POST)
    @ResponseBody
    public Result setOptAtt(@RequestBody GeotrellisTileOptions opt, @PathVariable("layerId") String layerId, @PathVariable("bandIndex") String bandIndex, HttpSession session) {
        try {
            int index = Integer.valueOf(bandIndex);
            MapService mapService = mapServiceService.select(layerId);
            MSFieldInfoSearch filter = new MSFieldInfoSearch();
            filter.setServiceID(layerId);
            filter.setBandIndex(index);//默认读取波段0
            MSFieldInfo msFieldInfo = msFieldInfoService.search(filter, new Page()).getResult().get(0);
            GeotrellisTileOptions fieldsColorMap = null;
            if (opt.getColorRampIndex() > 0) {
                fieldsColorMap = opt;
            } else {
                if (!msFieldInfo.getColorMap().equals("")) {
                    switch (msFieldInfo.getFieldType()) {
                        case "DString":
                            fieldsColorMap = convertFromDString(msFieldInfo);
                            break;
                        case "DDouble":
                            fieldsColorMap = convertFromDDouble(msFieldInfo);
                            break;
                        case "CDouble":
                            fieldsColorMap = convertFromCDouble(msFieldInfo);
                            break;
                    }
                } else {
                    fieldsColorMap = initFromDB(msFieldInfo);
                }
            }
            ColorMap.Options options = new ColorMap.Options(edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.getClassBoundaryType(), 0x00000000, 0x00000000, false);
            ColorMap cm = null;
            switch (fieldsColorMap.getFieldType()) {
                case "CDouble":
                    BigInteger sc = new BigInteger(fieldsColorMap.getStartColor(), 16);
                    BigInteger ec = new BigInteger(fieldsColorMap.getEndColor(), 16);
                    int startColor = sc.intValue();
                    int endColor = ec.intValue();
                    int min = fieldsColorMap.getMin();
                    int max = fieldsColorMap.getMax();
                    int stops = fieldsColorMap.getStops();
                    double gap = (max - min) * 1.0 / stops;
                    double[] stopVals = new double[stops];
                    for (int i = 0; i < stopVals.length; i++) {
                        stopVals[i] = (min + i * gap);
                    }
                    cm = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.getCustomContinouesColorMap(startColor, endColor, min, max, stopVals, options);
                    break;
                case "DString":
                    cm = catchColorMapStr(layerId, msFieldInfo, fieldsColorMap, options);
                    break;
                case "DDouble":
                    cm = catchColorMap(fieldsColorMap, options);
                    break;
                default:
                    return Result.error("Type Error: " + fieldsColorMap.getFieldType());
            }
            msFieldInfo.setColorMap(cm.breaksString());
            msFieldInfoService.update(msFieldInfo);
            return Result.success().setBody(fieldsColorMap);
        } catch (Exception e) {
            return Result.error().setMessage(e.getMessage());
        }
    }

    @RequestMapping(value = "/{layerId}/geotrellis/{z}/{x}/{y}/{bi}", method = RequestMethod.GET)
    @ResponseBody
    public void getGeotrellisTile(@PathVariable("layerId") String layerId, @PathVariable("z") Integer z,
                                  @PathVariable("y") Integer y, @PathVariable("x") Integer x, @PathVariable("bi") Integer bandIndex,
                                  HttpServletResponse response) throws IOException {
        MapService mapService = mapServiceService.select(layerId);
        MSFieldInfoSearch filter = new MSFieldInfoSearch();
        filter.setServiceID(layerId);
        filter.setBandIndex(bandIndex);//默认读取波段0
        MSFieldInfo msFieldInfo = msFieldInfoService.search(filter, new Page()).getResult().get(0);

        String attributeStorePath = "";
        String mapServiceName = "";
        ColorMap cm = null;

        mapServiceName = mapService.getName();

        cm = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.colorMapFromStr(msFieldInfo.getColorMap());
        attributeStorePath = "/opt/shengting/userSpace/" + mapService.getPublisherID() + "/mapServices";
//        attributeStorePath = "C:\\Data\\dbfg\\userSpace\\" + mapService.getPublisherID() + "\\mapServices";

        AttributeStore attributeStore = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.getAttributeStore(attributeStorePath);
        if (attributeStore == null || cm == null) {
            logger.error("No att or opt.");
            return;
        }
        Png png = null;

        if (z <= DEFAULT_Z_MAX) {
            png = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.
                    getTilePng(x, y, z, mapServiceName, attributeStore, attributeStorePath, bandIndex, cm);
//            png = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.
//                    getTileRGBPng(x, y, z, mapServiceName, attributeStore, attributeStorePath, 0,1,2);
        } else if (z > DEFAULT_Z_MAX && z <= 18) {
            png = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.
                    getResampleTilePng(x, y, z, DEFAULT_Z_MAX, mapServiceName, attributeStore, attributeStorePath, bandIndex, cm);
        } else {
            logger.error("unvalid zlevel, must <= 18, however the z = " + z);
        }


        /**
         * 写出到 response
         */
        ServletOutputStream outputStream = response.getOutputStream();

        if (png == null) {
            byte[] msg = String.format("Geotrellis tile [%d, %d, %d] for layer %s not exists", z, x, y, layerId).getBytes();
            IOUtils.write(msg, outputStream);
            response.setContentLength(msg.length);
            response.setStatus(404);
        } else {
            byte[] result = png.bytes();
            int filesize = result.length;
            response.setContentLength(filesize);
            response.setContentType(MediaType.IMAGE_PNG_VALUE);
            IOUtils.write(result, outputStream);
            outputStream.flush();
            outputStream.close();
        }
    }

    @RequestMapping(value = "/{layerId}/geotrellis/{z}/{x}/{y}/{bi}/{bi}/{bi}", method = RequestMethod.GET)
    @ResponseBody
    public void getGeotrellisTile(@PathVariable("layerId") String layerId, @PathVariable("z") Integer z,
                                  @PathVariable("y") Integer y, @PathVariable("x") Integer x,
                                  @PathVariable("bi") Integer bandIndex_1, @PathVariable("bi") Integer bandIndex_2, @PathVariable("bi") Integer bandIndex_3,
                                  HttpServletResponse response) throws IOException {
        MapService mapService = mapServiceService.select(layerId);
        String attributeStorePath = "";
        String mapServiceName = "";
        mapServiceName = mapService.getName();
        attributeStorePath = "/opt/shengting/userSpace/" + mapService.getPublisherID() + "/mapServices";
//        attributeStorePath = "C:\\Data\\dbfg\\userSpace\\" + mapService.getPublisherID() + "\\mapServices";

        AttributeStore attributeStore = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.getAttributeStore(attributeStorePath);
        if (attributeStore == null) {
            logger.error("No att or opt.");
            return;
        }
        Png png = null;

        if (z <= DEFAULT_Z_MAX) {
            png = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.
                    getTileRGBPng(x, y, z, mapServiceName, attributeStore, attributeStorePath, bandIndex_1, bandIndex_2, bandIndex_3);
        } else {
            logger.error("unvalid zlevel, must <= 18, however the z = " + z);
        }


        /**
         * 写出到 response
         */
        ServletOutputStream outputStream = response.getOutputStream();

        if (png == null) {
            byte[] msg = String.format("Geotrellis tile [%d, %d, %d] for layer %s not exists", z, x, y, layerId).getBytes();
            IOUtils.write(msg, outputStream);
            response.setContentLength(msg.length);
            response.setStatus(404);
        } else {
            byte[] result = png.bytes();
            int filesize = result.length;
            response.setContentLength(filesize);
            response.setContentType(MediaType.IMAGE_PNG_VALUE);
            IOUtils.write(result, outputStream);
            outputStream.flush();
            outputStream.close();
        }
    }

    private ColorMap catchColorMapStr(String layerId, MSFieldInfo msFieldInfo, GeotrellisTileOptions fieldsColorMap, ColorMap.Options options) {
        Map<Double, Integer> colorMap = new HashMap<>();
        Set<String> keys = fieldsColorMap.getColorMap().keySet();

        String[] items = msFieldInfo.getFieldValue().split("##");
        Map<String, String> pairs = new HashMap<>();
        for (String item : items) {
            String[] kv = item.split(",");
            pairs.put(kv[0], kv[1]);
        }

        for (String key : keys) {
            String k = pairs.get(key);
            String v = fieldsColorMap.getColorMap().get(key);
            BigInteger bi = new BigInteger(v, 16);
            int temp = bi.intValue();
            colorMap.put(Double.valueOf(k), temp);
        }
        return edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.transformColorMap(colorMap, options);
    }

    private ColorMap catchColorMap(GeotrellisTileOptions fieldsColorMap, ColorMap.Options options) {
        Map<Double, Integer> colorMap = new HashMap<>();
        Set<String> keys = fieldsColorMap.getColorMap().keySet();
        while (keys.iterator().hasNext()) {
            String val = keys.iterator().next();
            colorMap.put(Double.valueOf(val), Integer.valueOf(fieldsColorMap.getColorMap().get(val)));
        }
        return edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.transformColorMap(colorMap, options);
    }

    private GeotrellisTileOptions convertFromDString(MSFieldInfo msFieldInfo) {
        String[] items = msFieldInfo.getFieldValue().split("##");
        Map<Double, String> pairs = new HashMap<>();
        for (String item : items) {
            String[] kv = item.split(",");
            pairs.put(Double.valueOf(kv[1]), kv[0]);
        }
        Map<String, String> maps = new HashMap<>();
        String[] cmPairs = msFieldInfo.getColorMap().split(";");
        for (String cmPair : cmPairs) {
            String[] pair = cmPair.split(":");
            maps.put(pairs.get(Double.valueOf(pair[0])), pair[1]);
        }
        GeotrellisTileOptions fieldsColorMap = new GeotrellisTileOptions();
        fieldsColorMap.setColorRampIndex(1);
        fieldsColorMap.setColorMap(maps);
        fieldsColorMap.setFieldType(msFieldInfo.getFieldType());
        return fieldsColorMap;
    }

    private GeotrellisTileOptions convertFromDDouble(MSFieldInfo msFieldInfo) {
        Map<String, String> maps = new HashMap<>();
        String[] cmPairs = msFieldInfo.getColorMap().split(";");
        for (String cmPair : cmPairs) {
            String[] pair = cmPair.split(":");
            maps.put(pair[0], pair[1]);
        }
        GeotrellisTileOptions fieldsColorMap = new GeotrellisTileOptions();
        fieldsColorMap.setColorRampIndex(1);
        fieldsColorMap.setColorMap(maps);
        fieldsColorMap.setFieldType(msFieldInfo.getFieldType());
        return fieldsColorMap;
    }

    private GeotrellisTileOptions convertFromCDouble(MSFieldInfo msFieldInfo) {
        String[] startEnd = msFieldInfo.getColorMap().split(";");
        String[] minMax = msFieldInfo.getFieldValue().split("##");
        Double min = Double.valueOf(minMax[0]);
        Double max = Double.valueOf(minMax[1]);

        double _min = Double.valueOf(startEnd[0].split(":")[0]);
        double _max = Double.valueOf(startEnd[0].split(":")[0]);

        Map<Double, String> pairs = new HashMap<>();
        for (int i = 0; i < startEnd.length; i++) {
            String[] pair = startEnd[i].split(":");
            double temp = Double.valueOf(pair[0]);
            pairs.put(temp, pair[1]);
            if (temp < _min)
                _min = temp;
            if (temp > _max)
                _max = temp;
        }
        GeotrellisTileOptions fieldsColorMap = new GeotrellisTileOptions();
        fieldsColorMap.setMin(min.intValue());
        fieldsColorMap.setMax(max.intValue());
        fieldsColorMap.setStartColor(pairs.get(_min));
        fieldsColorMap.setEndColor(pairs.get(_max));
        fieldsColorMap.setFieldType(msFieldInfo.getFieldType());
        fieldsColorMap.setStops(startEnd.length);
        fieldsColorMap.setColorRampIndex(1);
        return fieldsColorMap;
    }

    private GeotrellisTileOptions initFromDB(MSFieldInfo msFieldInfo) {
        GeotrellisTileOptions fieldsColorMap = new GeotrellisTileOptions();
        fieldsColorMap.setFieldType(msFieldInfo.getFieldType());
        if (fieldsColorMap.getFieldType().equals("CDouble")) {
            String[] vs = msFieldInfo.getFieldValue().split("##");
            int min = Double.valueOf(vs[0]).intValue();
            int max = Double.valueOf(vs[1]).intValue() + 1;
            fieldsColorMap.setMin(min);
            fieldsColorMap.setMax(max);
        } else {
            ColorMap.Options options = new ColorMap.Options(edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.getClassBoundaryType(), 0x00000000, 0x00000000, false);
            String[] vs = msFieldInfo.getFieldValue().split("##");
            if (msFieldInfo.getFieldType().equals("DString")) {
                List<Double> temp = new ArrayList<>();
                for (Double i = 0.0; i < vs.length; i++)
                    temp.add(i);
                Map<String, String> fieldColor = new HashMap<>();
                ColorMap tempCm = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.getDiscreteRandomColorMap(temp, options);
//                value:color;value:color;...
                String[] pairs = tempCm.breaksString().split(";");
                for (int i = 1; i < pairs.length + 1; i++) {
                    String name = "N";
                    for (String p : vs) {
                        if (p.split(",")[1].equals(String.valueOf(i))) {
                            name = p.split(",")[0];
                            break;
                        }
                    }
                    fieldColor.put(name, pairs[i - 1].split(":")[1]);
                }
                fieldsColorMap.setColorMap(fieldColor);
            } else {
                List<Double> catchValue = new ArrayList<>();
                for (String v : vs) {
                    catchValue.add(Double.valueOf(v));
                }
                Map<String, String> fieldColor = new HashMap<>();
                ColorMap tempCm = edu.zju.gis.dbfg.tileserver.scala.GeoTrellisTileServer.getDiscreteRandomColorMap(catchValue, options);
                String[] pairs = tempCm.breaksString().split(";");
                for (int i = 0; i < pairs.length; i++) {
                    fieldColor.put(pairs[i].split(":")[0], pairs[i].split(":")[1]);
                }
                fieldsColorMap.setColorMap(fieldColor);
            }
        }
        fieldsColorMap.setColorRampIndex(1);
        return fieldsColorMap;
    }
}